AI 시대의 소스코드 품질
- 2025-08-19
- 저자: AK
AI 시대에도 소스 코드의 품질은 여전히 중요하다고 생각한다. 단기적인 이유와 장기적인 이유가 있다.
단기적인 이유
- 아직은 AI가 인간을 모방하고 있는 상황이기 때문에 인간이 잘 다루는 코드를 AI도 잘 다룰 것이다. 따라서 코드 품질은 여전히 중요할 것. (Welcome to the Era of Experience의 구분에 따르면 아직은 “인간 데이터의 시대”이기 때문)
- 코드 품질이 낮아지면(긴 코드, 순환 참조, 높은 결합도 등) LLM의 컨텍스트에 올릴 토큰 수가 많아진다. LLM이 지원하는 컨텍스트 크기는 꾸준히 늘고 있지만 컨텍스트가 길어지면 LLM이 엉뚱한 짓을 하는 현상(context rot)이 벌어진다. (상대적으로 사소한 문제이지만, 토큰을 많이 쓰면 비용도 더 들고 시간도 더 걸린다)
- AI가 코드를 빠르게 생산하기 시작하면서 병목이 코딩에서 리뷰로 옮겨지고 있다. 이 병목을 해소하려면 AI가 더 큰 단위의 작업을 붙들고 오래 일하고 결과물이 인간이 검토하기 좋은 형태로 나와야 한다. 토큰과 시간(AI의 시간)을 더 쓰더라도 품질 좋은(따라서 검토하기 좋은) 코드가 만들어지도록 유도하는 게 좋다.
장기적인 이유
장기적으로는 AI가 굳이 인간을 모방할 필요가 없으니 인간 기준의 “품질 좋은 코드”가 의미 없다고 생각할 수도 있다. 굳이 텍스트 기반의 “소스 코드”를 만들고 이걸 기계 코드로 바꾸는 과정이 불필요해질 수도 있다.
하지만 이런 상황이 된다고 하더라도 여전히 품질 좋은 코드는 중요할거라고 본다. 두 가지 이유가 있다.
- 첫째, 재귀적 분해와 조합, 모듈 간 격리 등의 특성은 인간의 이해가능성과 무관하게 그 자체로 좋은 속성이다. 이건 추측인데 인간의 마음이 재귀적 분해와 조합에 익숙하게끔 진화한 이유도 애초에 세상과 의미있는 상호작용을 하려면 그런 능력이 필요하기 때문일 것. 즉, 재귀적 분해와 조립은 인간 마음에 국한된 특성이 아니라고 생각한다. 다만 인간의 이해가능성과 호환되지 않는 모듈 간 위계, 모듈 간 인터페이스의 형태 등도 존재할 것.
- 둘째, 어차피 미래에도 인간이 (AI의 도움을 받아) 소프트웨어 감사를 하긴 해야할텐데(법적/윤리적 책임 관점에서) 이를 위해서는 인간이 이해가능한 코드가 여전히 필요하다. 다만 이런 맥락에서 “코드의 품질”이 무엇을 의미하는지에 대한 세세한 기준은 바뀔 수 있겠다. 예를 들면 “읽기가 얼마나 용이한가”가 “수정이 얼마나 용이한가”에 비해 (지금보다 훨씬 더) 중요해진다거나.
AI가 생성한 코드의 품질을 강제하기
에이전트 기반 코딩을 하거나 바이브 코딩을 하는 경우 코드 품질이 크게 낮아지는 경우가 있다.
하지만 소스 코드의 품질을 정량적으로 평가하거나 특정 설계를 강제할 수 있는 도구들이 제법 있으니 이런 것들을 잘 활용하면 품질을 일정 수준 이상으로 관리할 수 있다. AI 모델이 얼마나 똑똑해지는지도 중요하지만, 모델에게 적절한 도구들을 제공하여 모델이 일을 잘 할 수 있는 인지적 적소를 구축해주는 일 또한 매우 중요하다.
다음은 내가 사용해본 제법 괜찮은 조합. 토큰과 AI 시간을 더 쓰더라도 인간이 리뷰하기 좋은 코드를 만드는 게 중요(인간의 리뷰가 전체 프로세스의 병목이므로)하다는 가정 하에 괜찮았다. 자바스크립트 기준이지만 다른 언어를 쓰는 경우에도 대체로 유사한 도구가 있다.
- 기본적인 정적 타입 검사:
tsc --noEmit
- 린터를 이용한 추가 제약:
typescript-eslint
는recommended
말고strictTypeChecked
를 사용- 파일 최대 길이, 함수 최대 길이, 함수 최대 문장 수 제약
- 인지복잡도 제약(
eslint-plugin-sonarcode
) switch
문에서 모든 분기 검사(@typescript/switch-exhaustiveness-check
), 매직 넘버 금지(@typescript-eslint/no-magic-numbers
, 일부 예외 허용) 등
- 의존 관계 강제:
- 비즈니스 로직이 렌더링 관련 코드에 의존하지 못하도록 하기(
dependency-cruiser
) - Feature-Sliced Design을 하는 경우 각 feature가 독립적으게 강제하기(
dependency-cruiser
) - 각 모듈이 너무 많은 모듈에 의존하지 못하게 하기(
eslint-plugin-import
) - 그 밖에 내가 원한 설계 기준을 벗어나지 못하도록 강제
- 비즈니스 로직이 렌더링 관련 코드에 의존하지 못하도록 하기(
- 코드 중복 검사:
jscpd
등으로 구조적으로 동일한 코드 덩어리가 있으면 에러가 발생하도록 강제. 덩어리의 크기는 적당히 설정. - 테스트 커버리지: 단위 테스트와 인수 테스트와 테스트 커버리지 측정(
vitest
등)하고 특정 기준 미만이면 오류를 내기 - 죽은 코드 검사: 의도적으로 그렇게 훈련시킨 것 같은데, 코딩 에이전트를 코드를 지우는 일을 좀처럼 하지 않는다. 안쓰는 코드를 자동으로 검사해서 지우도록 할 필요가 있다.
knip
등 사용 - 테스트-프로덕션 코드 비율 강제. AI에게 단위 테스트를 만들어달라고 시키면 같은 경로를 반복적으로 지나고 동일한 로직을 중복해서 검사하는 테스트를 너무 많이 만드는 경향이 있다. 테스트 대 프로덕션 코드 비율이 특정 수준(예: 150%, 인수 테스트를 포함하는 경우 180%)을 넘지 않도록 강제하면 이런 문제를 막을 수 있다. (
tokei
등)
여러 도구를 잘 조합하는 게 중요하다. 원래도 있던 문제지만 AI 에이전트를 쓰는 경우 문제가 더 극단적으로 발생하는 경향이 있다. 예시:
- 테스트-프로덕션 코드 비율을 강제하지 않으면서 테스트 커버리지만 검사하면 테스트 갯수가 한없이 늘어나는 문제가 있다.
- 인지복잡도를 측정하지 않으면서 중복만 계속 제거하라고 하면 로직이 한없이 복잡해진다.